import * as THREE from "three"
import { ChangeAction } from "../collection/CollectionMonitor"
import { SetMouseCursorCommand } from "../command/cursor.command"
import { SaveCommand } from "../command/data.command"
import {
  MeasurementContentsReplace,
  MeasurementDeleteCommand,
  MeasurementDeleteSelectedCommand,
  MeasurementListDeletionCommand,
  MeasurementModeToggle,
  MeasurementNewline,
  MeasurementRenameCommand,
  MeasurementSelect,
  MeasurementSelectById,
  MeasurementStop,
  MeasurementVisibilityCommand
} from "../command/measurements.command"
import { ToggleMeshOverlayColorCommand } from "../command/mesh.command"
import { RegisterDeletedObjectTypeCommand } from "../command/object.command"
import { ToggleToolCommand } from "../command/tool.command"
import IntersectionVisualizer from "../component/IntersectionVisualizer"
import LineDerivedDataFactory from "../component/LineDerivedDataFactory"
import MeasurementCreatorDesktop from "../component/MeasurementCreatorDesktop"
import MeasurementCreatorMobile from "../component/MeasurementCreatorMobile"
import MeasurementJointEditor from "../component/MeasurementJointEditor"
import MeasurementLineStyler from "../component/MeasurementLineStyler"
import SnappableAxisUpdater from "../component/SnappableAxisUpdater"
import SnappablePointer from "../component/SnappablePointer"
import SnappablePointUpdater from "../component/SnappablePointUpdater"
import keyConst from "../constant/key.const"
import ApplicationData from "../data/ApplicationData"
import CameraData from "../data/CameraData"
import FloorsViewData from "../data/FloorsViewData"
import MeasurementModeData from "../data/MeasurementModeData"
import PlayerOptionsData from "../data/PlayerOptionsData"
import RoomData from "../data/RoomData"
import SettingsData from "../data/SettingsData"
import SweepData from "../data/SweepData"
import ViewmodeData from "../data/ViewmodeData"
import EngineContext from "../EngineContext"
import { appTypeMin } from "../enum/appType.enum"
import cursorEnum from "../enum/cursor.enum"
import dataTypeEnum from "../enum/dataType.enum"
import hotKeyEnum, { KeyState } from "../enum/hotKey.enum"
import measurementEnum, { measurementAction, measurementFeatureType } from "../enum/measurement.enum"
import measuringPhaseEnum from "../enum/measuringPhase.enum"
import renderOrderEnum from "../enum/renderOrder.enum"
import { SceneChildType } from "../enum/scene.enum"
import storageTypeEnum from "../enum/storageType.enum"
import { ToolsList } from "../enum/tool.enum"
import unitTypeEnum from "../enum/unitType.enum"
import { DragEvent } from "../event/DragEvent"
import { KeyEvent } from "../event/KeyEvent"
import Le from "../math/13188"
import P from "../math/27687"
import y from "../math/7170"
import MeasurementInput from "../mesh/MeasurementInput"
import MeasurementLabelBackgroundMesh from "../mesh/MeasurementLabelBackgroundMesh"
import ModelColliderTarget from "../mesh/ModelColliderTarget"
import {
  MeasurementCreatedMessage,
  MeasurementCreationCancelledMessage,
  MeasurementDeletedMessage,
  MeasurementModeToggledMessage,
  MeasurementSegmentCreatedMessage,
  MeasurementTitleAddedMessage,
  MeasurementTitleChangedMessage,
  MeasurementUpdatedMessage
} from "../message/measurement.message"
import { ToggleViewingControlsMessage } from "../message/tool.message"
import DeletedObject from "../object/DeletedObject"
import { createArray } from "../observer/observable.array"
import re from "../packages/60039"
import MeasurementLabelRenderer from "../renderer/MeasurementLabelRenderer"
import MeasurementLineRenderer from "../renderer/MeasurementLineRenderer"
import SymbolList from "../SymbolList"
import Label3DFactory from "../three/Label3DFactory"
import WebGLScene from "../three/WebGLScene"
import MeasurementsManager from "../tools/MeasurementsManager"
import browserUtil from "../utils/browser.util"
import ConstrainedHit from "../utils/ConstrainedHit"
import Group from "../utils/Group"
import Grouper from "../utils/Grouper"
import Logger from "../utils/Logger"
import { Catalog, Subscription } from "../utils/Subscription"
import CursorControllerModule from "./CursorControllerModule"
import InputIniModule, { InputSubscription } from "./InputIniModule"
import LinesModule from "./LinesModule"
import MeshQueryModule from "./MeshQueryModule"
import Module from "./Module"
import NavigationModule from "./NavigationModule"
import RaycasterModule from "./RaycasterModule"
import RenderToTextureModule from "./RenderToTextureModule"
import WebGLRendererModule from "./WebGLRendererModule"

const te = (e, t) => {
  const i = t & measurementAction.ADDED,
    s = t & measurementAction.REMOVED,
    n = t & measurementAction.UPDATED,
    r: any = {}
  let o
  return new Catalog(
    () => e,
    t => (o = e.onElementChanged((e => ((r.onAdded = i ? e : void 0), (r.onRemoved = s ? e : void 0), (r.onUpdated = n ? e : void 0), r))(t))),
    () => {
      o && o.cancel()
    }
  )
}

const log = new Logger("measurement-mode")
export default class MeasurementModeModule extends Module {
  longPressStart: number
  threshold: number
  lineSidToPointMap: Record<string, number>
  mobile: boolean
  cameraAndDragBlocked: boolean
  store: any
  trashEnabled: boolean
  engine: EngineContext
  lineDerivedDataFactory: LineDerivedDataFactory
  pointGroups: Grouper
  data: MeasurementModeData
  colliders: MeasurementInput
  mutationRecord: Record<string, Set<string>>
  dragInterceptor: InputSubscription
  navigation: NavigationModule
  settings: SettingsData
  viewmodeData: ViewmodeData
  constraint: number
  previousPhase: number
  intersectionVisualizer: IntersectionVisualizer
  pointer: SnappablePointer
  editor: MeasurementJointEditor
  lineStyler: MeasurementLineStyler
  renderer: MeasurementLineRenderer
  textRenderer: MeasurementLabelRenderer
  scene: WebGLScene
  creator: MeasurementCreatorMobile | MeasurementCreatorDesktop
  config: any
  playerOptions: PlayerOptionsData
  floorsViewData: FloorsViewData
  roomData: RoomData
  input: InputIniModule
  meshQueryModule: MeshQueryModule
  dataSubscription: Subscription
  applicationData: ApplicationData
  constructor() {
    super(...arguments)
    this.name = "measurement-mode"
    this.longPressStart = Date.now()
    this.threshold = 800
    this.lineSidToPointMap = {}
    this.mobile = !1
    this.cameraAndDragBlocked = !1
    this.store = null
    this.trashEnabled = !1
    this.mutationRecord = {
      [ChangeAction.added]: new Set(),
      [ChangeAction.updated]: new Set(),
      [ChangeAction.removed]: new Set()
    }
  }
  blockNavigation() {
    return false
  }
  changeCursor(e) {
    this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(e))
  }
  replaceContents(e) {
    this.lineDerivedDataFactory.clear()
    e ? this.pointGroups.copy(e) : this.pointGroups.reset()
  }
  onEdit(e) {
    const t = this.data.getGroupInfo(e),
      i = (t && t.info.sid) || null
    this.data.editingGroupId !== i && ((this.data.editingGroupId = i), this.colliders.setEditingGroup(e))
  }
  onEditEnd() {
    if (this.data.editingGroupId) {
      const e = this.pointGroups.getGroupById(this.data.editingGroupId)
      if (e) {
        this.mutationRecord.updated.add(e.info.sid)
        this.save()
        this.engine.broadcast(new MeasurementUpdatedMessage(this.buildAnalyticsMessageFromGroup(e)))
      }
    }
    this.data.editingGroupId = null
    this.colliders.setEditingGroup(void 0)
    this.stopMeasuring()
  }
  onCreatorAddNewLine(e: string) {
    log.debug("onCreatorAddNewLine", e)
    this.data.creatingGroupId = e
    this.data.commit()
  }
  onCreatorAddPoint() {
    if (this.data.creatingGroupId) {
      log.debug("onCreatorAddNewSegment", this.data.creatingGroupId)
      const e = this.pointGroups.getGroupById(this.data.creatingGroupId)
      if (e && e.count > 1 && e.length > 0) {
        const t = this.buildAnalyticsMessageFromGroup(e)
        this.engine.broadcast(
          new MeasurementSegmentCreatedMessage(
            Object.assign(Object.assign({}, t), {
              startPosition: this.pointGroups.get(e.startIndex),
              endPosition: this.pointGroups.get(e.endIndex)
            })
          )
        )
      }
    }
  }
  onCreatorStop() {
    if (this.data.creatingGroupId) {
      const e = this.pointGroups.getGroupById(this.data.creatingGroupId)
      if (e) {
        if (e.count > 1 && e.hasLength()) {
          this.engine.broadcast(new MeasurementCreatedMessage(Object.assign({}, this.buildAnalyticsMessageFromGroup(e))))
          this.mutationRecord.added.add(e.info.sid)
          this.save()
        } else {
          this.engine.broadcast(new MeasurementCreationCancelledMessage())
        }
      }
    }
    this.data.creatingGroupId = null
    this.data.commit()
  }
  async onToggleMeasurementMode(e, t) {
    this.toggleMeasuringMode(e)
    await this.engine.commandBinder.issueCommand(new ToggleMeshOverlayColorCommand(e && t))
  }
  onViewmodeChange() {
    this.getPhase() !== measuringPhaseEnum.CLOSED && this.stopMeasuring()
  }
  onSweepChange() {
    const e = this.getPhase()
    e !== measuringPhaseEnum.CREATING && e !== measuringPhaseEnum.CREATING_NEXT_POINT && this.setSelected(-1)
  }
  getPhase() {
    return this.data.phase
  }
  toggleCameraMovement(e: boolean) {
    e || this.cameraAndDragBlocked
      ? e &&
        this.cameraAndDragBlocked &&
        (this.dragInterceptor.cancel(), this.navigation.removeNavigationRule(this.blockNavigation), (this.cameraAndDragBlocked = !1))
      : (this.dragInterceptor.renew(), this.navigation.addNavigationRule(this.blockNavigation), (this.cameraAndDragBlocked = !0))
  }
  getConstraint() {
    return this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementSnapping, !1)
      ? this.viewmodeData.isFloorplan()
        ? re.xh.floorplan
        : this.constraint
      : re.xh.disabled
  }
  getSelected() {
    return this.data.selectedGroupIndex
  }
  setSelected(e: number) {
    this.data.selectedGroupIndex !== e && this.data.setSelectedGroupIndex(e)
  }
  async setSelectedById(e: string) {
    if (null === e) this.setSelected(-1)
    else {
      const t = this.pointGroups.getGroupById(e)
      t && this.setSelected(t.index)
    }
  }
  deleteSelectedMeasurement() {
    if (this.data.selectedGroupIndex > -1) {
      this.deleteMeasurement(this.data.selectedGroupIndex)
      this.setSelected(-1)
      this.changePhase(measuringPhaseEnum.IDLE)
      if (!this.mobile) {
        this.navigation.removeNavigationRule(this.blockNavigation)
        this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.DEFAULT))
      }
    }
  }
  deleteMeasurement(e: number, t = !1) {
    const i = this.buildAnalyticsMessage(e)
    i &&
      this.engine.broadcast(
        new MeasurementDeletedMessage(
          Object.assign(Object.assign({}, i), {
            count: this.pointGroups.groupCount - 1
          })
        )
      )
    const s = this.pointGroups.getGroup(e)
    if (s) {
      this.pointGroups.removeGroup(e)
      s.info.sid !== this.data.creatingGroupId && (this.mutationRecord.removed.add(s.info.sid), t || this.save())
    }
  }
  async deleteMeasurementBySids(e) {
    const t = e.sids
    for (const i of t) {
      const t = this.pointGroups.getGroupById(i)
      if (!t) throw (log.error("Measurement delete failed", Object.assign({}, e)), Error("Measurement delete failed, not found"))
      this.deleteMeasurement(t.index, !0)
    }
    this.save()
  }
  onToggleContinuous() {
    this.stopMeasuring()
  }
  changePhase(e: number) {
    this.getPhase() !== e &&
      (log.debug(`Phase Change: ${measuringPhaseEnum[this.getPhase()]} -> ${measuringPhaseEnum[e]}`),
      (this.previousPhase = this.getPhase()),
      this.data.setPhase(e))
  }
  restorePreviousPhase() {
    this.changePhase(this.previousPhase)
  }
  onPhaseChange(e: number) {
    this.intersectionVisualizer.setMeasuringPhase(e)
    if (this.previousPhase !== e)
      switch (e) {
        case measuringPhaseEnum.CLOSED:
        case measuringPhaseEnum.IDLE:
          this.engine.broadcast(new ToggleViewingControlsMessage(!0))
          break
        case measuringPhaseEnum.EDITING:
        case measuringPhaseEnum.CREATING:
        case measuringPhaseEnum.POINT_PLACED:
        case measuringPhaseEnum.CREATING_NEXT_POINT:
        case measuringPhaseEnum.CONFIRMING_POINT:
          this.engine.broadcast(new ToggleViewingControlsMessage(!1))
      }
  }
  buildAnalyticsMessage(e: number) {
    const t = this.pointGroups.getGroup(e)
    return t && t.hasLength() ? this.buildAnalyticsMessageFromGroup(t) : null
  }
  buildAnalyticsMessageFromGroup(e: Group) {
    const t = this.viewmodeData.isFloorplan() ? measurementEnum[measurementEnum.FloorplanOnly] : measurementEnum[measurementEnum.ThreeD]
    return Object.assign(
      Object.assign(
        {
          sid: e.info && e.info.sid ? e.info.sid : e.describe(),
          totalLength: e ? e.length : 0,
          segments: e ? e.count : 0,
          temporary: !e.info || e.info.sid === e.describe(),
          viewmode: this.viewmodeData.currentMode,
          floorId: e.info.floorId,
          continuous: this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementContinuousLines, !1)
        },
        this.getAnalyticsForConstraints()
      ),
      { type: t }
    )
  }
  getAnalyticsForConstraints() {
    const e = this.pointer.lastIntersection
    if (e) {
      const t = e.featureType,
        i = e.constraint
      return {
        featureType: void 0 !== t ? measurementFeatureType[t] : "None",
        constraint: Le.l1[i]
      }
    }
    return null
  }
  toggleMeasuringMode(e: boolean) {
    log.debug("toggleMeasuringMode", e)
    e !== (this.getPhase() !== measuringPhaseEnum.CLOSED) &&
      (e
        ? (this.engine.toggleRendering(this, !0),
          this.changePhase(measuringPhaseEnum.IDLE),
          this.editor.activate(),
          this.lineStyler.activate(),
          this.colliders.activate(),
          this.renderer.activate(),
          this.textRenderer.activate(),
          this.scene.addChild(SceneChildType.Root, this.textRenderer.container),
          this.engine.broadcast(new MeasurementModeToggledMessage(!0, this.viewmodeData.currentMode, this.pointGroups.groupCount)),
          this.pointer.preload())
        : (this.engine.toggleRendering(this, !1),
          this.stopMeasuring(),
          this.changePhase(measuringPhaseEnum.CLOSED),
          this.toggleCameraMovement(!0),
          this.editor.deactivate(),
          this.lineStyler.deactivate(),
          this.colliders.deactivate(),
          this.renderer.deactivate(),
          this.textRenderer.deactivate(),
          this.scene.removeChild(SceneChildType.Root, this.textRenderer.container),
          this.engine.broadcast(new MeasurementModeToggledMessage(!1, this.viewmodeData.currentMode, this.pointGroups.groupCount))))
  }
  startMeasuring() {
    if (!this.mobile) {
      this.navigation.addNavigationRule(this.blockNavigation)
      this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.XHAIR))
    }
    this.creator.start()
  }
  stopMeasuring() {
    const e = this.isMeasurementComplete(this.data.selectedGroupIndex)
    this.isCreating() && !e && this.deleteSelectedMeasurement()
    this.creator.stop()
    if (!this.mobile) {
      this.navigation.removeNavigationRule(this.blockNavigation)
      this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.DEFAULT))
    }
  }
  setupIntersectionVisuals(
    e: RenderToTextureModule,
    t: CursorControllerModule,
    i: WebGLScene,
    s: CameraData,
    n: SnappablePointer,
    r: boolean,
    o: EngineContext["getRenderLayer"],
    a: ViewmodeData
  ) {
    t.addVisibilityRule(() => {
      const e = this.getPhase()
      return !(
        (!r && e === measuringPhaseEnum.CREATING) ||
        e === measuringPhaseEnum.CONFIRMING_POINT ||
        e === measuringPhaseEnum.CREATING_NEXT_POINT ||
        e === measuringPhaseEnum.EDITING
      )
    })
    const h = { cameraData: s, playerCamera: i.camera, scene: i }
    return new IntersectionVisualizer(r, n, h, e, o, this.engine.commandBinder.issueCommand, a.isFloorplan)
  }
  clearMutationRecord() {
    for (const e of Object.values(this.mutationRecord)) e["clear"]()
  }
  async renameMeasurement(e) {
    const t = this.pointGroups.getGroupById(e.sid)
    if (!t) throw (log.error("Measurement rename failed", Object.assign({}, e)), Error("Measurement rename failed, not found"))
    const i = void 0 !== e.text && e.text.length <= 24
    if (!t || !i)
      throw (
        (log.error("Measurement text invalid, text must be between 0 and 24 charachters in length.", Object.assign({}, e)),
        new Error("Measurement text invalid"))
      )
    {
      const i = "" === t.info.text && e.text.length > 0,
        s = !i && t.info.text !== e.text
      this.pointGroups.updateGroupInfo(t.index, Object.assign(Object.assign({}, t.info), { text: e.text })),
        this.mutationRecord.updated.add(t.info.sid),
        this.save(),
        i && this.engine.broadcast(new MeasurementTitleAddedMessage(e.sid, e.text)),
        s && this.engine.broadcast(new MeasurementTitleChangedMessage(e.sid, t.info.text, e.text))
    }
  }
  async onChangeVisibility(e) {
    for (const t of e.sids) {
      const i = this.pointGroups.getGroupById(t)
      if (!i)
        throw (
          (log.error("Measurement visibility toggle failed", Object.assign(Object.assign({}, e), { group: i })),
          new Error("Measurement visibility toggle failed, not found"))
        )
      {
        this.pointGroups.updateGroupInfo(
          i.index,
          Object.assign(Object.assign({}, i.info), {
            visible: e.visible
          })
        ),
          this.mutationRecord.updated.add(i.info.sid)
        const t = this.buildAnalyticsMessage(i.index)
        t && this.engine.broadcast(new MeasurementUpdatedMessage(t))
      }
    }
    this.save()
  }
  async init(e, t: EngineContext) {
    this.config = e
    this.mobile = browserUtil.isMobile()
    this.engine = t
    const [i, s, a, l, d, u, w, f, v, T] = await Promise.all([
      t.getModuleBySymbol<WebGLRendererModule>(SymbolList.WEBGL_RENDERER),
      t.getModule(RenderToTextureModule),
      t.getModuleBySymbol<InputIniModule>(SymbolList.INPUT),
      t.getModule(LinesModule),
      t.getModule(RaycasterModule),
      t.market.waitForData(FloorsViewData),
      t.market.waitForData(CameraData),
      t.market.waitForData(ViewmodeData),
      t.market.waitForData(SweepData),
      t.market.waitForData(RoomData)
    ])
    this.settings = await t.market.waitForData(SettingsData)
    this.playerOptions = await t.market.waitForData(PlayerOptionsData)
    this.scene = i.getScene()
    this.viewmodeData = f
    this.floorsViewData = u
    this.roomData = T
    this.input = a
    this.navigation = await t.getModule(NavigationModule)
    this.meshQueryModule = await t.getModuleBySymbol<MeshQueryModule>(SymbolList.MESH_QUERY)
    this.lineDerivedDataFactory = new LineDerivedDataFactory(
      w,
      f,
      u,
      () => !!v.currentSweep && v.isSweepAligned(v.currentSweep),
      () => this.settings.tryGetProperty(keyConst.rule.user.preference.UnitType, unitTypeEnum.IMPERIAL),
      () => !0
    )
    const P = t.claimRenderLayer(this.name)
    this.data = new MeasurementModeData()
    t.market.register(this, MeasurementModeData, this.data)
    const D = createArray([])
    this.pointGroups = new Grouper(D)
    const b = (e, t, i = !1) => te(D, e).createSubscription(t, i)
    this.dataSubscription = b(
      measurementAction.ALL,
      () => {
        this.data.repopulate(this.pointGroups.groups())
        this.data.commit()
      },
      !0
    )
    this.applicationData = await t.market.waitForData(ApplicationData)
    this.constraint = this.mobile ? re.xh.mobile : re.xh.desktop
    this.pointer = new SnappablePointer(d, this.getConstraint.bind(this), this.mobile, this.floorsViewData, this.viewmodeData)
    const M = new SnappablePointUpdater(this.pointGroups, b, this.pointer, () => {
      const e = this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementContinuousLines, !1),
        t =
          this.getPhase() === measuringPhaseEnum.CREATING ||
          this.getPhase() === measuringPhaseEnum.CREATING_NEXT_POINT ||
          (this.previousPhase === measuringPhaseEnum.CREATING_NEXT_POINT && this.getPhase() === measuringPhaseEnum.EDITING)
      return e && this.mobile && t
    })
    t.addComponent(this, M)
    const x = new SnappableAxisUpdater(this.pointer, this.mobile, this.getPhase.bind(this), this.scene, t.getRenderLayer)
    t.addComponent(this, x)
    this.creator = this.instantiateCreator(w)
    const I = (e: number, t: number, i: number) => {
      const s = this.pointGroups.getGroup(e)
      const n = s.describe(i)
      this.lineSidToPointMap[n] = i
      const r = this.lineDerivedDataFactory.get(n)
      return this.lineDerivedDataFactory.make(
        n,
        () => ({
          start_position: this.pointGroups.get(t),
          end_position: this.pointGroups.get(i),
          visible: s.info.visible,
          floorId: s.info.floorId,
          roomId: s.info.roomId,
          type: s.info.type,
          text: this.pointGroups.isStartIndex(t) ? s.info.text : ""
        }),
        r
      )
    }
    this.renderer = new MeasurementLineRenderer(this.pointGroups, b, w, l, P, t.claimRenderLayer("measure-lines"), this.getSelected.bind(this), I)
    await t.addComponent(this, this.renderer)
    const R = new Label3DFactory({
      color: "black",
      background: !0,
      backgroundColor: "#ffffff",
      backgroundColliderType: MeasurementLabelBackgroundMesh
    })
    this.textRenderer = new MeasurementLabelRenderer(
      this.pointGroups,
      a,
      this.mobile,
      b,
      w,
      P,
      renderOrderEnum.labels,
      R,
      I,
      this.changeCursor.bind(this),
      this.getPhase.bind(this),
      this.setSelected.bind(this)
    )
    await t.addComponent(this, this.textRenderer)
    this.textRenderer.deactivate()
    this.editor = new MeasurementJointEditor(this.pointGroups, a, this.pointer, this.getPhase.bind(this), this.onEdit.bind(this), this.onEditEnd.bind(this))
    this.colliders = new MeasurementInput(
      this.pointGroups,
      this.input,
      w,
      this.mobile,
      this.changeCursor.bind(this),
      this.getPhase.bind(this),
      this.changePhase.bind(this),
      this.restorePreviousPhase.bind(this),
      this.setSelected.bind(this),
      this.getSelected.bind(this),
      this.onEdit.bind(this),
      this.onEditEnd.bind(this)
    )
    this.lineStyler = new MeasurementLineStyler(
      this.renderer.lines,
      this.renderer.getLinesForPoint,
      this.renderer.dottedMaterial,
      b,
      this.input,
      this.getPhase.bind(this),
      this.getSelected.bind(this)
    )
    const C = await t.getModule(CursorControllerModule)
    this.intersectionVisualizer = this.setupIntersectionVisuals(s, C, this.scene, w, this.pointer, this.mobile, t.getRenderLayer, this.viewmodeData)
    t.addComponent(this, this.intersectionVisualizer)
    this.dragInterceptor = this.input.registerPriorityHandler(DragEvent, ModelColliderTarget, () => !0)
    this.dragInterceptor.cancel()
    this.mobile || this.bindings.push(...this.hotkeys())
    this.bindings.push(
      f.makeModeChangeSubscription(this.onViewmodeChange.bind(this)),
      v.makeSweepChangeSubscription(this.onSweepChange.bind(this)),
      t.commandBinder.addBinding(MeasurementModeToggle, async e => this.onToggleMeasurementMode(e.on, e.dimWhileActive)),
      t.commandBinder.addBinding(MeasurementNewline, async () => this.startMeasuring()),
      t.commandBinder.addBinding(MeasurementSelect, async e => this.setSelected(e.index)),
      t.commandBinder.addBinding(MeasurementStop, async () => this.stopMeasuring()),
      t.commandBinder.addBinding(MeasurementDeleteSelectedCommand, async () => this.deleteSelectedMeasurement()),
      t.commandBinder.addBinding(MeasurementDeleteCommand, async e => this.deleteMeasurement(e.index)),
      t.commandBinder.addBinding(MeasurementSelectById, async e => this.setSelectedById(e.sid)),
      t.commandBinder.addBinding(MeasurementVisibilityCommand, this.onChangeVisibility.bind(this)),
      t.commandBinder.addBinding(MeasurementRenameCommand, this.renameMeasurement.bind(this)),
      t.commandBinder.addBinding(MeasurementListDeletionCommand, this.deleteMeasurementBySids.bind(this)),
      t.commandBinder.addBinding(MeasurementContentsReplace, async e => {
        return this.replaceContents(e.points?.groups())
      }),
      this.dataSubscription,
      this.settings.onPropertyChanged(keyConst.rule.user.preference.MeasurementContinuousLines, () => this.onToggleContinuous()),
      this.data.onPhaseChanged(this.onPhaseChange.bind(this))
    )
    t.toggleRendering(this, !1)
    this.updateSettings()
    // setTimeout(async () => {
    //   await new MeasurementsManager(this.engine, this.settings).activate()
    //   this.startMeasuring()
    // }, 1000)
  }

  isCreating() {
    const e = this.getPhase()
    return e === measuringPhaseEnum.CREATING || e === measuringPhaseEnum.CREATING_NEXT_POINT
  }
  setConstraintStyle(e) {
    log.debug("setConstraintStyle:", Le.l1[e]), (this.constraint = e)
  }
  isMeasurementComplete(e) {
    if (-1 === e || !this.isCreating()) return !0
    const t = this.getPhase(),
      i = this.pointGroups.getGroup(e)
    return this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementContinuousLines, !1)
      ? i.count > 2 && t === measuringPhaseEnum.CREATING_NEXT_POINT
      : i.count >= 2 && t === measuringPhaseEnum.CREATING
  }
  hotkeys() {
    return [
      this.input.registerHandler(KeyEvent, e => {
        if (e.state === KeyState.PRESSED)
          switch (e.key) {
            case hotKeyEnum.ESCAPE:
              this.getPhase() === measuringPhaseEnum.CONFIRMING_POINT
                ? (this.creator.stop(), this.creator.start(), this.changePhase(measuringPhaseEnum.CREATING))
                : this.isCreating()
                ? this.stopMeasuring()
                : this.applicationData.application !== appTypeMin.WORKSHOP &&
                  this.getPhase() !== measuringPhaseEnum.CLOSED &&
                  this.engine.commandBinder.issueCommand(new ToggleToolCommand(ToolsList.MEASUREMENTS, !1))
              break
            case hotKeyEnum.BACKSPACE:
            case hotKeyEnum.DELETE:
              this.getPhase() !== measuringPhaseEnum.CLOSED && this.getPhase() !== measuringPhaseEnum.EDITING && this.deleteSelectedMeasurement()
              break
            case hotKeyEnum.RETURN:
              const e = this.pointGroups.groupCount - 1
              this.isMeasurementComplete(e) && this.stopMeasuring()
          }
      }),
      this.input.registerHandler(KeyEvent, e => {
        if (e.key === hotKeyEnum.SHIFT || e.key === hotKeyEnum.ALT) {
          const { altKey: t, shiftKey: i } = e.modifiers,
            s = i && t ? re.xh.shiftAlt : t ? re.xh.alt : i ? re.xh.shift : re.xh.desktop
          this.setConstraintStyle(s)
        }
      })
    ]
  }
  onUpdate(e) {
    if (this.getPhase() === measuringPhaseEnum.CLOSED) return
    const t = this.data.selectedGroupIndex,
      i = !!this.mobile
    for (const s in this.lineSidToPointMap) {
      const n = this.lineDerivedDataFactory.get(s)
      if (n) {
        n.opacity.tick(e)
        const r = this.lineSidToPointMap[s],
          o = this.pointGroups.groupFromPointIndex(r),
          a = o === t && n.visible ? 1 : n.opacity.value,
          h = i && !n.labelVisible ? 0 : a
        this.textRenderer.setTextOpacityByPoint(r, h), this.renderer.setLineOpacityByPoint(r, a), -1 !== o && this.colliders.setGroupVisible(o, a > 0)
      }
    }
    if (this.mobile)
      if (this.getPhase() === measuringPhaseEnum.CONFIRMING_POINT) {
        const e = (Date.now() - this.longPressStart) / this.threshold
        e <= 1 && this.data.setPressProgress(e)
      } else if (this.getPhase() === measuringPhaseEnum.CREATING && 0 !== this.data.pressProgress) {
        const e = 0
        this.data.setPressProgress(e)
      }
  }
  dispose(e) {
    this.stopMeasuring(),
      this.engine.commandBinder.issueCommand(new MeasurementModeToggle(!1)),
      this.colliders.dispose(),
      this.textRenderer.dispose(),
      this.renderer.dispose(),
      e.disposeRenderLayer(this.name),
      super.dispose(e)
  }
  async save() {
    if (!this.config.readonly) return this.engine.commandBinder.issueCommand(new SaveCommand({ dataTypes: [dataTypeEnum.MEASUREMENTS] }))
  }

  updateSettings() {
    const i = this.settings.tryGetProperty(re.Ql, !0),
      s = i
    this.settings.setProperty(re.Ql, s)
  }
  instantiateCreator(e) {
    let t: MeasurementCreatorMobile | MeasurementCreatorDesktop
    const i = e => {
      const t = { floorId: "", roomId: "" }
      this.meshQueryModule.inferMeshIdsFromPoint(t, e)
      return t
    }
    if (this.mobile) {
      const setCreatePointProgress = (e: number, t: number) => {
        this.longPressStart = e
        this.threshold = t
      }
      const updateCreatePointHit = (t: ConstrainedHit) => {
        const i = P.q9(e, t.point)
        this.data.setPointPosition(i.screenPosition)
      }
      t = new MeasurementCreatorMobile(
        this.pointGroups,
        this.setSelected.bind(this),
        this.input.registerUnfilteredHandler,
        this.pointer,
        this.changePhase.bind(this),
        setCreatePointProgress,
        updateCreatePointHit,
        this.toggleCameraMovement.bind(this),
        this.getPhase.bind(this),
        () => this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementContinuousLines, !1),
        this.viewmodeData.isFloorplan,
        this.floorsViewData.getHighestVisibleFloorId,
        () => this.roomData.selected.value,
        i
      )
    } else
      t = new MeasurementCreatorDesktop(
        this.pointGroups,
        this.setSelected.bind(this),
        this.input.registerUnfilteredHandler,
        this.pointer,
        this.changePhase.bind(this),
        this.getPhase.bind(this),
        this.viewmodeData.isFloorplan,
        this.floorsViewData.getHighestVisibleFloorId,
        () => this.roomData.selected.value,
        () => this.settings.tryGetProperty(keyConst.rule.user.preference.MeasurementContinuousLines, !1),
        i
      )
    return (
      (t.onGroupCreated = this.onCreatorAddNewLine.bind(this)),
      (t.onGroupAddPoint = this.onCreatorAddPoint.bind(this)),
      (t.onEdit = this.onEdit.bind(this)),
      (t.onDone = this.onCreatorStop.bind(this)),
      t
    )
  }
  async configureTrash() {
    const { readonly: e, storageProvider: t } = this.config
    if (this.trashEnabled || e || t === storageTypeEnum.JSONSTORE) return
    const { commandBinder: i } = this.engine,
      s = dataTypeEnum.MEASUREMENTS,
      n = {
        objectType: s,
        create: e =>
          new DeletedObject({
            label: e.label || "Measurement",
            objectId: e.id,
            objectType: s,
            created: new Date(),
            value: e
          }),
        restore: () => {}
      }
    this.trashEnabled = !0
    const r = await i.issueCommandWhenBound(new RegisterDeletedObjectTypeCommand(n))
    this.bindings.push(r)
  }
}
